package com.corewillsoft.fireflies.storage.objects;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import android.text.TextUtils;

import com.corewillsoft.fireflies.gameobjects.obstacles.Obstacle.ObstacleParameterType;
import com.corewillsoft.fireflies.gameobjects.obstacles.Obstacle.ObstacleParametersList;
import com.corewillsoft.fireflies.gameobjects.obstacles.Obstacle.ObstacleParameters;

public abstract class BaseGameLevel implements IContentValuesConvertable {

	/**
	 * Delimiter string which separates parameters sets of different obstacles,
	 * it is used in the obstacles parameters string (which is saved in DB),
	 * for example: "1T55,77I3T999,888,777I" string determines parameters for three obstacles:
	 * first obstacles has parameters 55 and 77 of type '1',
	 * second one has parameters 999, 888 and 777 of type '3',
	 * and the third obstacle doesn't have any parameters
	 */
	private static final String OBSTACLES_PARAMETERS_DELIMITER = "I";

	/**
	 * Delimiter string which separates obstacle parameters sets of different types (one obstacle can have different
	 * sets of parameters of different types,
	 * possible parameters types are determined by
	 * {@link com.corewillsoft.fireflies.gameobjects.obstacles.Obstacle.ObstacleParameterType}),
	 * delimiter is used in the obstacles parameters string (which is saved in DB),
	 * for example: "1T55,77P2T123,456" string determines two sets of obstacle parameters: one having type '1' and
	 * parameters 55 and 77,
	 * second set of parameters has type '2' and parameters 123 and 456
	 */
	private static final String OBSTACLES_PARAMETERS_LISTS_DELIMITER = "P";

	/**
	 * Delimiter string which separates type of obstacle parameters set and sequence of corresponding parameters,
	 * it is used in the obstacles parameters string (which is saved in DB),
	 * for example: "1T55,77" string determines one set of obstacle parameters having type '1' and parameters 55 and 77;
	 * possible parameters types are determined by
	 * {@link com.corewillsoft.fireflies.gameobjects.obstacles.Obstacle.ObstacleParameterType})
	 */
	private static final String OBSTACLES_PARAMETERS_TYPE_DELIMITER = "T";

	/**
	 * Delimiter string which separates obstacle parameters,
	 * it is used in the obstacles parameters string (which is saved in DB),
	 * for example: "1T55,77" string determines one set of obstacle parameters having type '1' and parameters 55 and 77
	 */
	private static final String OBSTACLES_PARAMETERS_VALUES_DELIMITER = ",";

	protected int level;
	protected List<Float> fireflyEnergy;
	protected List<Boolean> fireflyAlive;
	protected List<Boolean> fireflySaved;
	protected List<Integer> fireflyIds;
	protected List<Integer> fireflyX;
	protected List<Integer> fireflyY;
	protected List<Integer> portalIds;
	protected List<Integer> portalX;
	protected List<Integer> portalY;
	protected List<Integer> obstacleIds;
	protected List<Integer> obstacleX;
	protected List<Integer> obstacleY;

	/**
	 * List of all obstacles' parameters for this level;
	 * each element of this list contains parameters for the corresponding obstacle in the level
	 */
	protected List<ObstacleParameters> obstaclesParameters;

	/**
	 * Instance of class-converter for serialization/deserialization of obstacles' parameters for the level
	 */
	private final ObstaclesParametersConverter obstaclesParametersConverter = new ObstaclesParametersConverter();

	public BaseGameLevel() {

	}

	public BaseGameLevel(BaseGameLevel gameLevel) {
		setFireflyEnergy(gameLevel.getFireflyEnergy());
		setFireflyAlive(gameLevel.getFireflyAlive());
		setFireflySaved(gameLevel.getFireflySaved());
		setFireflyIds(gameLevel.getFireflyIds());
		setFireflyX(gameLevel.getFireflyX());
		setFireflyY(gameLevel.getFireflyY());
		setPortalIds(gameLevel.getPortalIds());
		setPortalX(gameLevel.getPortalX());
		setPortalY(gameLevel.getPortalY());
		setObstacleIds(gameLevel.getObstacleIds());
		setObstacleX(gameLevel.getObstacleX());
		setObstacleY(gameLevel.getObstacleY());
		setObstaclesParameters(gameLevel.getObstaclesParameters());
	}

	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public List<Integer> getFireflyIds() {
		return fireflyIds;
	}

	public void setFireflyIds(List<Integer> fireflyIds) {
		this.fireflyIds = fireflyIds;
	}

	public List<Integer> getFireflyX() {
		return fireflyX;
	}

	public void setFireflyX(List<Integer> fireflyX) {
		this.fireflyX = fireflyX;
	}

	public List<Integer> getFireflyY() {
		return fireflyY;
	}

	public void setFireflyY(List<Integer> fireflyY) {
		this.fireflyY = fireflyY;
	}

	public abstract List<Float> getFireflyEnergy();

	public void setFireflyEnergy(List<Float> fireflyEnergy) {
		this.fireflyEnergy = fireflyEnergy;
	}

	public abstract List<Boolean> getFireflyAlive();

	public void setFireflyAlive(List<Boolean> fireflyAlive) {
		this.fireflyAlive = fireflyAlive;
	}

	public abstract List<Boolean> getFireflySaved();

	public void setFireflySaved(List<Boolean> fireflySaved) {
		this.fireflySaved = fireflySaved;
	}

	public List<Integer> getPortalIds() {
		return portalIds;
	}

	public void setPortalIds(List<Integer> portalIds) {
		this.portalIds = portalIds;
	}

	public List<Integer> getPortalX() {
		return portalX;
	}

	public void setPortalX(List<Integer> portalX) {
		this.portalX = portalX;
	}

	public List<Integer> getPortalY() {
		return portalY;
	}

	public void setPortalY(List<Integer> portalY) {
		this.portalY = portalY;
	}

	public List<Integer> getObstacleIds() {
		return obstacleIds;
	}

	public void setObstacleIds(List<Integer> obstacleIds) {
		this.obstacleIds = obstacleIds;
	}

	public List<Integer> getObstacleX() {
		return obstacleX;
	}

	public void setObstacleX(List<Integer> obstacleX) {
		this.obstacleX = obstacleX;
	}

	public List<Integer> getObstacleY() {
		return obstacleY;
	}

	public void setObstacleY(List<Integer> obstacleY) {
		this.obstacleY = obstacleY;
	}

	public List<ObstacleParameters> getObstaclesParameters() {
		return obstaclesParameters;
	}

	public void setObstaclesParameters(List<ObstacleParameters> obstaclesParameters) {
		this.obstaclesParameters = obstaclesParameters;
	}

	/**
	 * Method serializes list of all obstacles' parameters of the level to string
	 * (used to save obstacles parameters to the DB)
	 * @return string like "1T55,77I3T999,888,777I", containing serialized parameters of all obstacles of the level
	 *         (to understand structure of this string, see examples in the description of
	 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_DELIMITER},
	 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_LISTS_DELIMITER},
	 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_TYPE_DELIMITER},
	 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_VALUES_DELIMITER})
	 */
	public String getObstaclesParametersString() {
		return obstaclesParametersConverter.getObstaclesParametersString();
	}

	/**
	 * Method deserializes all obstacles' parameters in this level, returns list containing different obstacles'
	 * parameters
	 * by the string containing these parameters
	 * @param parametersString
	 *            - string containing parameters of all obstacles (for format of this string, see example in the
	 *            description of {@link BaseGameLevel#OBSTACLES_PARAMETERS_DELIMITER},
	 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_LISTS_DELIMITER},
	 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_TYPE_DELIMITER},
	 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_VALUES_DELIMITER})
	 * @param obstaclesCount
	 *            - amount of obstacles in the level (if parametersString contains parameters not for all obstacles,
	 *            empty elements of other obstacles will be created in resulted list, so this list will always
	 *            have obstaclesCount elements)
	 * @return list containing parameters for each particular obstacles in the level
	 */
	public List<ObstacleParameters> stringToObstaclesParameters(String parametersString, int obstaclesCount) {
		return obstaclesParametersConverter.stringToObstaclesParameters(parametersString, obstaclesCount);
	}

	/**
	 * Utility class used for serialization to string/deserialization of obstacles' parameters for the level
	 */
	private class ObstaclesParametersConverter {

		/**
		 * Method serializes list of all obstacles' parameters of the level to string
		 * (used to save obstacles parameters to the DB)
		 * @return string like "1T55,77I3T999,888,777I", containing serialized parameters of all obstacles of the level
		 *         (to understand structure of this string, see examples in the description of
		 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_DELIMITER},
		 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_LISTS_DELIMITER},
		 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_TYPE_DELIMITER},
		 *         {@link BaseGameLevel#OBSTACLES_PARAMETERS_VALUES_DELIMITER})
		 */
		public String getObstaclesParametersString() {
			if (obstaclesParameters == null) {
				return "";
			}
			String resultString = "";

			// loop for getting and serializing parameters of each obstacle
			for (ObstacleParameters obstacleParametersMap : obstaclesParameters) {
				if (!obstacleParametersMap.isEmpty()) {

					/*
					 * loop for getting parameters set type and parameters list for current considered obstacle;
					 * result of this loop is addition of string like "1T123,456P2777,888,999" to result string
					 */
					for (Entry<ObstacleParameterType, ObstacleParametersList> obstacleParametersElement : obstacleParametersMap
							.entrySet()) {
						ObstacleParameterType parameterType = obstacleParametersElement.getKey();
						ObstacleParametersList parametersList = obstacleParametersElement.getValue();
						resultString = resultString + parameterType.ordinal() + OBSTACLES_PARAMETERS_TYPE_DELIMITER;
						resultString = resultString
								+ TextUtils.join(OBSTACLES_PARAMETERS_VALUES_DELIMITER, parametersList)
								+ OBSTACLES_PARAMETERS_LISTS_DELIMITER;
					}
					resultString = resultString.substring(0, resultString.length() - 1); // delete last parameters list
																						 // delimiter because it is
																						 // useless
				}
				resultString = resultString + OBSTACLES_PARAMETERS_DELIMITER;
			}

			// delete last obstacles delimiter in the string because we don't have obstacle parameters after it, so it
			// is useless
			if (resultString.length() > 0) {
				resultString = resultString.substring(0, resultString.length() - 1);
			}

			return resultString;
		}

		/**
		 * Method deserializes all obstacles' parameters in this level, returns list containing different obstacles'
		 * parameters
		 * by the string containing these parameters
		 * @param parametersString
		 *            - string containing parameters of all obstacles (for format of this string, see example in the
		 *            description of {@link BaseGameLevel#OBSTACLES_PARAMETERS_DELIMITER},
		 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_LISTS_DELIMITER},
		 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_TYPE_DELIMITER},
		 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_VALUES_DELIMITER})
		 * @param obstaclesCount
		 *            - amount of obstacles in the level (if parametersString contains parameters not for all obstacles,
		 *            empty elements of other obstacles will be created in resulted list, so this list will always
		 *            have obstaclesCount elements)
		 * @return list containing parameters for each particular obstacles in the level
		 */
		public List<ObstacleParameters> stringToObstaclesParameters(String parametersString, int obstaclesCount) {
			List<ObstacleParameters> allObstaclesParametersList = new ArrayList<ObstacleParameters>();

			if (!parametersString.equals("")) {
				String[] allObstaclesParametersArray = parametersString.split(OBSTACLES_PARAMETERS_DELIMITER);

				// deserialize parameters for each obstacle in the input string
				for (String obstacleAllParametersString : allObstaclesParametersArray) {
					ObstacleParameters obstacleParametersMap = stringToObstacleParametersMap(obstacleAllParametersString);
					allObstaclesParametersList.add(obstacleParametersMap);
				}
			}

			// if there were parameters not for all obstacles, add empty parameters maps for other obstacles
			int obstaclesWithProvidedParameters = allObstaclesParametersList.size();
			if (obstaclesWithProvidedParameters < obstaclesCount) {
				for (int i = obstaclesWithProvidedParameters; i < obstaclesCount; i++) {
					allObstaclesParametersList.add(new ObstacleParameters());
				}
			}

			return allObstaclesParametersList;
		}

		/**
		 * Deserializes string containing parameters of one obstacle, to the map containing lists of obstacle parameters
		 * of different types
		 * @param parametersString
		 *            - string, containing parameters of one obstacle (for example, "1T123,456P2777,888,999")
		 * @return map, containing all parameters (of different types) for one obstacle
		 */
		private ObstacleParameters stringToObstacleParametersMap(String parametersString) {
			ObstacleParameters obstacleParametersMap = new ObstacleParameters();

			if (!parametersString.equals("")) {
				String[] obstacleDifferentTypesOfParametersArray = parametersString
						.split(OBSTACLES_PARAMETERS_LISTS_DELIMITER);
				for (String parametersOfParticularTypeString : obstacleDifferentTypesOfParametersArray) {
					String[] parametersOfParticularTypeArray = parametersOfParticularTypeString
							.split(OBSTACLES_PARAMETERS_TYPE_DELIMITER);

					String parameterTypeString = parametersOfParticularTypeArray[0];
					ObstacleParameterType parameterType = ObstacleParameterType.values()[Float.valueOf(
							parameterTypeString).intValue()];

					String parameters = parametersOfParticularTypeArray[1];
					ObstacleParametersList parametersList = stringToObstacleParametersList(parameters);

					obstacleParametersMap.put(parameterType, parametersList);
				}
			}

			return obstacleParametersMap;
		}

		/**
		 * Deserializes string containing parameters of one type to the list of these parameters
		 * @param parametersString
		 *            - string containing float values divided by delimiter
		 *            {@link BaseGameLevel#OBSTACLES_PARAMETERS_VALUES_DELIMITER}, for example, "123,456,789"
		 * @return list of deserilized parameters
		 */
		private ObstacleParametersList stringToObstacleParametersList(String parametersString) {
			ObstacleParametersList parametersList = new ObstacleParametersList();

			if (!parametersString.equals("")) {
				String[] parametersArray = parametersString.split(OBSTACLES_PARAMETERS_VALUES_DELIMITER);
				for (String stringsArrayElement : parametersArray) {
					parametersList.add(Float.valueOf(stringsArrayElement));
				}
			}

			return parametersList;
		}
	}

}